﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Xml.Serialization;
using jQube.Resources;
using System.Reflection;
using System.Collections;

namespace jQube.Web.JSon.Serialization
{
    public sealed class JSonSerializer
    {
        private const string JSonDelimiter = ",";
        private int depthCount;
        private Type type;

        public JSonSerializer(Type type)
        {
            if (!this.IsJSonSerializable(type))
            {
                throw new ArgumentException(
                    string.Format(AtlasWeb.JSonSerializer_TypeIsNotJSonSerializableType, type.Name), "type");
            }

            this.type = type;
            this.depthCount = 0;
        }

        internal JSonSerializer(Type type, int depthCount)
        {
            if (!this.IsJSonSerializable(type))
            {
                throw new ArgumentException(
                    string.Format(AtlasWeb.JSonSerializer_TypeIsNotJSonSerializableType, type.Name), "type");
            }

            this.type = type;
            this.depthCount = depthCount;
        }

        internal Type Type
        {
            get
            {
                return this.type;
            }
        }

        internal int DepthCount
        {
            get
            {
                return this.depthCount;
            }
        }

        private bool IsJSonSerializable(Type type)
        {
            JSonSerializableAttribute[] jsonSerializable = (JSonSerializableAttribute[])
                type.GetCustomAttributes(typeof(JSonSerializableAttribute), true);

            return (jsonSerializable != null && jsonSerializable.Length == 1) ||
                (type.IsValueType);
        }

        public void Serialize(JSonWriter writer, object instance)
        {
            if (!instance.GetType().Equals(this.Type))
            {
                throw new ArgumentException(
                    string.Format(AtlasWeb.JSonSerializer_InstanceDoesNotMatch, type.Name), "instance");
            }

            List<PropertyInfo> properties = this.GetPropertyList();

            if (!this.Type.IsValueType)
            {
                if (this.DepthCount > 3) return;

                writer.Write("{");

                foreach (PropertyInfo property in properties)
                {
                    JSonSerializer jsonSerializer = new JSonSerializer(property.DeclaringType);

                    JSonElementAttribute[] jsonElement = (JSonElementAttribute[])property
                        .GetCustomAttributes(typeof(JSonElementAttribute), true);

                    string elementName = property.Name;

                    if (jsonElement != null && jsonElement.Length == 1)
                    {
                        elementName = jsonElement[0].Name;

                        if (jsonElement[0].GetType().Equals(typeof(JSonArrayAttribute)))
                        {
                            IList list = property.GetValue(instance, null) as IList;

                            if (list == null)
                            {
                                throw new InvalidOperationException(
                                    string.Format(AtlasWeb.JSonSerializer_PropertyTypeDoesNotImplementsIListInterface, property.Name));
                            }

                            this.Serialize(writer, elementName, list);
                        }
                        else if (jsonElement[0].GetType().Equals(typeof(JSonObjectAttribute)))
                        {
                            object value = property.GetValue(instance, null);
                            this.Serialize(writer, elementName, value);
                        }
                        else if (jsonElement[0].GetType().Equals(typeof(JSonEnumAttribute)))
                        {
                            if (!property.DeclaringType.IsEnum)
                            {
                                throw new InvalidOperationException(
                                    string.Format(AtlasWeb.JSonSerializer_ProperyTypeIsNotAnEnumerator, property.Name));
                            }

                            JSonEnumAttribute jsonEnumAttribute = jsonElement[0] as JSonEnumAttribute;

                            object value = property.GetValue(instance, null);

                            if (value != null)
                            {
                                value = Convert.ChangeType(value, jsonEnumAttribute.BaseType);
                            }
                        }
                        else
                        {
                            object value = property.GetValue(instance, null);
                            this.Serialize(writer, elementName, value);
                        }
                    }
                }

                writer.Write("}");
            }
            else
            {
                if (instance.GetType().Equals(typeof(string)) ||
                    instance.GetType().Equals(typeof(char)) ||
                    instance.GetType().Equals(typeof(DateTime)))
                {
                    writer.Write(string.Format("\"{0}\"", instance.ToString()));
                }
                else if (instance.GetType().Equals(typeof(short)) ||
                        instance.GetType().Equals(typeof(int)) ||
                        instance.GetType().Equals(typeof(long)))
                {
                    writer.Write(instance.ToString());
                }
            }
        }

        internal void Serialize(JSonWriter writer, string elementName, IList list)
        {
            writer.Write(string.Format("\"{0}\":[", elementName));

            foreach (object value in list)
            {
                JSonSerializer jsonSerializer = new JSonSerializer(value.GetType(), this.DepthCount + 1);
                jsonSerializer.Serialize(writer, value);

                if (list.IndexOf(value) != list.Count - 1)
                {
                    writer.Write(JSonDelimiter);
                }
            }

            writer.Write("]");
        }

        internal void Serialize(JSonWriter writer, string elementName, object value)
        {
            writer.Write(string.Format("\"{0}\":", elementName));

            JSonSerializer jsonSerializer = new JSonSerializer(value.GetType(), this.DepthCount + 1);
            jsonSerializer.Serialize(writer, value);
        }

        public List<PropertyInfo> GetPropertyList()
        {
            List<PropertyInfo> serializableProperties = new List<PropertyInfo>();

            PropertyInfo[] properties = this.Type.GetProperties(BindingFlags.Public);

            foreach (PropertyInfo property in properties)
            {
                if (this.IsJSonSerializable(property.DeclaringType))
                {
                    JSonIgnoreAttribute[] jsonIgnore = (JSonIgnoreAttribute[])property
                    .GetCustomAttributes(typeof(JSonIgnoreAttribute), true);

                    if (jsonIgnore != null)
                    {
                        if (jsonIgnore.Length == 0)
                        {
                            serializableProperties.Add(property);
                        }
                    }
                    else
                    {
                        serializableProperties.Add(property);
                    }
                }
            }

            return serializableProperties;
        }
    }
}
